﻿


using IotClientService.Extensions;
using IotClientService.Handles;
using IotClientService.Models.Hbc;
using IotContract.Dtos.Clients.Equips;
using IotContract.Dtos.Servers;
using IotContract.Dtos.Servers.Pushs;
using IotContract.Enums;
using IotContract.Events;
using IotContract.Interfaces;
using IotContract.Models.Clients;
using Mapster;
using Newtonsoft.Json;
using SyncSocketModuleCore.SyncSocketModule;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using XT.Common.Extensions;

namespace IotClientService.Operations.plc;
public class HbcSiemensS7Operation : IEquipOperation<HbcSiemensEquipDto>
{
    public event EventHandler<ConnectStatusEventArgs> OnConnectStatusChanged;
    private CancellationTokenSource? _cancellationTokenSource;
    private IConnectDataService _connectDataService;
    private SyncSocketServices? _socketService;
   
    private HbcSiemensEquipDto _equipInfo;
    private bool _curConnection;
    private bool _status = false;
    private Task _operationTask;
    private ReaderWriterLockSlim m_UpdateReadWordInfoListLock = new ReaderWriterLockSlim();
    private PlcReadBlockManager _readBlockManager;

    public List<EquipTagDto> PLCTagList { get; set; } = new List<EquipTagDto>();
    /// <summary>
    /// 当前Read
    /// </summary>
    public List<PlcReadWord> CurReadWordList { get; set; } = new List<PlcReadWord>();

    /// <summary>
    /// 由taglist转换来
    /// </summary>
    public List<PlcReadWord> ReadWordList { get; set; } = new List<PlcReadWord>();
    /// <summary>
    /// 注入数据服务
    /// </summary>
    /// <param name="connectDataService"></param>
    public HbcSiemensS7Operation(IConnectDataService connectDataService)
    {
        _connectDataService=connectDataService;
        
    }
    /// <summary>
    /// 连接状态
    /// </summary>
    /// <returns></returns>
    public bool GetConnection()
    {
        return _curConnection;
    }
    /// <summary>
    /// 获取设备信息
    /// </summary>
    /// <returns></returns>
    public HbcSiemensEquipDto GetEquipInfo()
    {
        // 点位赋值
       var tags= PLCTagList.Adapt<List<PushEquipTag>>();
        _equipInfo.Tags = tags;
        return _equipInfo;
    }
    /// <summary>
    /// 轮询状态
    /// </summary>
    /// <returns></returns>
    /// <exception cref="NotImplementedException"></exception>
    public bool GetOperationStatus()
    {
        return _status;
    }
   
    /// <summary>
    /// 初始化数据
    /// </summary>
    /// <param name="group"></param>
    /// <param name="token"></param>
    /// <exception cref="NotImplementedException"></exception>
    public async Task<bool> Init(HbcSiemensEquipDto group,IEnumerable<EquipTagDto> tags=null)
    {
        try
        {
            _equipInfo = group;
            if (tags == null || tags.Count() == 0)
            {
                return false;
            }
            _cancellationTokenSource = new CancellationTokenSource();

            

            PLCTagList = tags.ToList();

            // 解析ipaddress
            var ips = _equipInfo.IPAddress.Split(":");
            if (ips.Length != 2)
            {
                return false;
            }
            // 初始化socket
            _socketService = new SyncSocketServices(_equipInfo.Name, _equipInfo.Num.To<int>(), ips[0], ips[1].To<int>(), ips[1].To<int>(), 5, 3000, 3000);
            _socketService.SetPacketHandler(new SiemensS7PacketHandler());




            // 赋值readword

            ReadWordList = HbcExtension.GetReadWordInfoListByPlcGroupNumber(PLCTagList);



            _socketService.OnSyncSocketServiceLogHandler += new EventHandler<SyncSocketServiceLogEventArgs>((o, e) =>
            {
                // 不打印错误日志
                return;
            });
            _socketService.OnConnectionChangedHandler += new EventHandler<ConnectChangeEventArgs>((o, e) =>
            {
                bool currentConnection = e.CurrentConnection;
                if (OnConnectStatusChanged == null)
                {
                    _curConnection = currentConnection;
                    return;
                }
                 ;
                if (_curConnection != currentConnection)
                {
                    _curConnection = currentConnection;
                    if (_curConnection)
                    {
                        OnConnectStatusChanged(this, new ConnectStatusEventArgs
                        {
                            Connect = currentConnection,
                            IsSuccess = true

                        });
                    }
                    
                }


            });
            _equipInfo.Remark = string.Empty;
            return true;
        }
        catch(Exception ex)
        {
           var error=$"{_equipInfo.ToString()} 初始化失败,原因：{ex.Message}";

            _equipInfo.Remark = error;
            OnConnectStatusChanged.Invoke(this, new ConnectStatusEventArgs
            {
                Connect = _curConnection,
                IsSuccess = false,
                Message = error
            });
            return false;
        }
    }
    /// <summary>
    /// 启动
    /// </summary>
    public async Task<bool> OperationStart()
    {
        if (_operationTask != null && _curConnection)
        {
            await OperationStop();

        }

        if (_cancellationTokenSource == null)
        {
            return false;
        }

        _socketService?.OperationStart(_cancellationTokenSource, _cancellationTokenSource.Token, _equipInfo.Pooling);

        _operationTask = Task.Factory.StartNew(() => ThreadScheduler(), _cancellationTokenSource.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

        _status = true;
        return true;
    }
    /// <summary>
    /// 停止
    /// </summary>
    /// <returns></returns>
    public async Task<bool> OperationStop()
    {
        _socketService?.OperationStop();

        await Task.Delay(200);

        _cancellationTokenSource?.Cancel();



        _status = false;
        return true;
    }
    /// <summary>
    /// 任务
    /// </summary>
    /// <returns></returns>
    private async Task ThreadScheduler()
    {

        while (true)
        {
            if (_cancellationTokenSource?.IsCancellationRequested == true)
            {
                break;
            }
            try
            {





                m_UpdateReadWordInfoListLock.EnterWriteLock();
                if (ReadWordList.Count > 0)
                {
                    SetReadWordInfoList(ReadWordList);
                    _readBlockManager = new PlcReadBlockManager();

                    _readBlockManager.MakeReadBlock(438, ReadWordList);
                    // 更新完成后清理
                    ReadWordList.Clear();
                }
                m_UpdateReadWordInfoListLock.ExitWriteLock();


                if (_readBlockManager == null) continue;


                ReadPlcData(_readBlockManager);

                ParsePlcData(_readBlockManager);

                if (_curConnection)
                {
                    NotifyPlcDataChanged();
                }


                await Task.Delay(_equipInfo.Pooling);
            }
            catch (Exception exception)
            {
                OnConnectStatusChanged?.Invoke(this, new ConnectStatusEventArgs
                {
                    Message = exception.Message,
                    Connect = _curConnection,
                    IsSuccess = false

                });
            }


        }
    }

    /// <summary>
    /// 构造 CurReadWordList
    /// </summary>
    /// <param name="readWordInfoList"></param>
    public void SetReadWordInfoList(List<PlcReadWord> readWordInfoList)
    {

        foreach (PlcReadWord connection in readWordInfoList)
        {
            PlcReadWord? readWordInfo1 = CurReadWordList.FirstOrDefault((readWordInfo) =>
            {
                if (readWordInfo.StartAddr != connection.StartAddr || readWordInfo.MemoryType != connection.MemoryType || readWordInfo.ReadCount != connection.ReadCount)
                {
                    return false;
                }
                return readWordInfo.DBBlock == connection.DBBlock;
            });
            if (readWordInfo1 != null)
            {
                readWordInfo1.UpdateSetTo(connection);
            }
            else
            {
                connection.OldConnection = GetConnection();
                connection.CurConnection = GetConnection();
            }
        }
        CurReadWordList = (
            from readWordInfo in readWordInfoList
            orderby readWordInfo.DBBlock, readWordInfo.StartAddr, readWordInfo.ReadCount descending
            select readWordInfo).ToList();

    }


    #region write or read

    /// <summary>
    /// 读取PLC数据
    /// </summary>
    private void ReadPlcData(PlcReadBlockManager blockManager)
    {
        if (blockManager == null) return;
        foreach (List<PlcReadBlock> allReadBlockList in blockManager.GetAllReadBlockList())
        {
            Parallel.ForEach(allReadBlockList, readBlock =>
            {
                bool? flag = _socketService?.ReadWord(readBlock.ReadBuffer, readBlock.StartAddr, readBlock.ReadWordCount, (int)readBlock.MemoryType, readBlock.DBBlock);
                readBlock.ReadComplete = flag == true;
            });

        }
    }
    /// <summary>
    /// 解析PLC数据
    /// </summary>
    /// <param name="blockManager"></param>
    private void ParsePlcData(PlcReadBlockManager blockManager)
    {
        if (blockManager == null) return;

        Parallel.ForEach(CurReadWordList, mCurReadWordInfo =>
        {
            PlcReadBlockManager mReadBlockManager = blockManager;
            int dBBlock = mCurReadWordInfo.DBBlock;
            PlcReadBlock readBlock = mReadBlockManager.GetReadBlock(dBBlock.ToString(), mCurReadWordInfo.StartAddr);
            if (readBlock == null)
            {
                return;
            }
            mCurReadWordInfo.OldReadComplete = mCurReadWordInfo.CurReadComplete;
            mCurReadWordInfo.CurReadComplete = readBlock.ReadComplete;
            if (!readBlock.ReadComplete || !_curConnection)
            {
                return;
            }
            Array.Copy(mCurReadWordInfo.CurReadValue, 0, mCurReadWordInfo.OldReadValue, 0, mCurReadWordInfo.CurReadValue.Length);
            int startAddr = (mCurReadWordInfo.StartAddr - readBlock.StartAddr) * 2;
            int readCount = mCurReadWordInfo.ReadCount * 2;
            if (startAddr + readCount > readBlock.ReadBuffer.Length)
            {
                return;
            }
            Array.Copy(readBlock.ReadBuffer, startAddr, mCurReadWordInfo.CurReadValue, 0, readCount);
        });

    }

    /// <summary>
    /// DataChanged
    /// </summary>
    private void NotifyPlcDataChanged()
    {

        Parallel.ForEach(CurReadWordList, mCurReadWordInfo =>
        {
            mCurReadWordInfo.OldConnection = mCurReadWordInfo.CurConnection;
            mCurReadWordInfo.CurConnection = _curConnection;
            if (!_curConnection)
            {
                mCurReadWordInfo.CurReadComplete = false;
            }


            SetTagData(mCurReadWordInfo);
        });
    }
    /// <summary>
    ///  tag赋值
    /// </summary>
    /// <param name="updateData"></param>
    public void SetTagData(PlcReadWord updateData)
    {
        string str = "";
        var matchingTargetList = PLCTagList.Where(x => x.Address == updateData.StartAddr.ToString() && x.AddrType == updateData.MemoryType && x.ReadBlock == updateData.DBBlock.ToString());


        if (matchingTargetList != null && matchingTargetList.Any())
        {
            Parallel.ForEach(matchingTargetList, (matchingTarget) =>
            {
                str = GetValue(updateData.CurReadValue, (PlcDataType)matchingTarget.DataType, matchingTarget.DataLength);
                if (matchingTarget.Value != str)
                {
                    matchingTarget.OldValue = matchingTarget.Value;
                    matchingTarget.ReadChangeTime = DateTime.Now;

                }            
                matchingTarget.ReadTime = DateTime.Now;
                matchingTarget.Value = str;
            });
        }
    }
    /// <summary>
    /// 读取数据
    /// </summary>
    /// <param name="ByteValue"></param>
    /// <param name="DataType"></param>
    /// <param name="DataTypeInfo"></param>
    /// <returns></returns>
    private string GetValue(byte[] byteValue, PlcDataType dataType, int dataLength)
    {
        string str = "";
        switch (dataType)
        {
            case PlcDataType.BIT:
                {
                    if (byteValue.Length <= 0)
                    {
                        break;
                    }
                    str = new BitArray(byteValue).Get(dataLength) ? "1" : "0";
                    break;
                }
            case PlcDataType.INT:
                {
                    if (dataLength != 1)
                    {
                        if (dataLength != 2 || byteValue.Length <= 0)
                        {
                            break;
                        }
                        str = BitConverter.ToInt32(byteValue, 0).ToString();
                        break;
                    }
                    else
                    {
                        if (byteValue.Length <= 0)
                        {
                            break;
                        }
                        str = BitConverter.ToUInt16(byteValue, 0).ToString();
                        break;
                    }
                }
            case PlcDataType.INT16:
                {
                    if (byteValue.Length <= 0)
                    {
                        break;
                    }
                    str = BitConverter.ToUInt16(byteValue, 0).ToString();
                    break;
                }
            case PlcDataType.FLOAT:
                {
                    if (dataLength != 2)
                    {
                        if (dataLength != 4 || byteValue.Length <= 0)
                        {
                            break;
                        }
                        str = BitConverter.ToDouble(byteValue, 0).ToString();
                        break;
                    }
                    else
                    {
                        if (byteValue.Length <= 0)
                        {
                            break;
                        }
                        str = BitConverter.ToSingle(byteValue, 0).ToString();
                        break;
                    }
                }
            case PlcDataType.STR:
                {
                    str = Encoding.UTF8.GetString(byteValue).Replace("\0", string.Empty);
                    break;
                }
            case PlcDataType.BIT4:
                {
                    if (byteValue.Length <= 0)
                    {
                        break;
                    }
                    switch (dataLength)
                    {
                        case 1:
                            {
                                int num = BitConverter.ToInt16(byteValue, 0) & 15;
                                str = num.ToString();
                                break;
                            }
                        case 2:
                            {
                                int num1 = (BitConverter.ToInt16(byteValue, 0) & 240) >> 4;
                                str = num1.ToString();
                                break;
                            }
                        case 3:
                            {
                                int num2 = (BitConverter.ToInt16(byteValue, 0) & 3840) >> 8;
                                str = num2.ToString();
                                break;
                            }
                        case 4:
                            {
                                int num3 = (BitConverter.ToInt16(byteValue, 0) & 61440) >> 12;
                                str = num3.ToString();
                                break;
                            }
                    }
                    break;
                }
            case PlcDataType.BIT2:
                {
                    if (byteValue.Length <= 0)
                    {
                        break;
                    }
                    switch (dataLength)
                    {
                        case 1:
                            {
                                int num4 = BitConverter.ToInt16(byteValue, 0) & 255;
                                str = num4.ToString();
                                break;
                            }
                        case 2:
                            {
                                int num5 = (BitConverter.ToInt16(byteValue, 0) & 65280) >> 8;
                                str = num5.ToString();
                                break;
                            }
                    }
                    break;
                }
        }
        return str;
    }

    private bool WriteBit4Value(EquipTagDto writeWordInfo,string value)
    {
        int[] numArray = new int[1];
        bool result = int.TryParse(value,out int writeValue);
        if (!result)
        {
            return false;
        }
        int num = 0;
        m_UpdateReadWordInfoListLock.EnterReadLock();
        PlcReadWord? readWordInfo = CurReadWordList.FirstOrDefault((n) =>
        {
            if (n.MemoryType != writeWordInfo.AddrType || n.StartAddr != writeWordInfo.Address.To<int>())
            {
                return false;
            }
            return n.ReadCount == 1;
        });
        if (readWordInfo == null) return false;
        num = BitConverter.ToInt16(readWordInfo.CurReadValue, 0);
        m_UpdateReadWordInfoListLock.ExitReadLock();
        numArray[0] = num;
        PlcUtil.SetBit4Mask(ref numArray, writeWordInfo.DataLength, writeValue);
        var result1 = _socketService?.WriteWord(numArray, writeWordInfo.Address.To<int>(), 1, (int)writeWordInfo.AddrType, writeWordInfo.ReadBlock.To<int>());
        return result1 == true;
    }

    private bool WriteBitValue(EquipTagDto writeWordInfo, string value )
    {
        int[] numArray = new int[1];
        bool result = int.TryParse(value, out int writeValue);
        if (!result)
        {
            return false;
        }
        int num = 0;
        m_UpdateReadWordInfoListLock.EnterReadLock();
        PlcReadWord? readWordInfo = CurReadWordList.FirstOrDefault((n) =>
        {
            if (n.MemoryType != writeWordInfo.AddrType || n.StartAddr != writeWordInfo.Address.To<int>())
            {
                return false;
            }
            return n.ReadCount == 1;
        });
        if (readWordInfo == null) return false;
        num = BitConverter.ToInt16(readWordInfo.CurReadValue, 0);
        m_UpdateReadWordInfoListLock.ExitReadLock();
        numArray[0] = num;
        PlcUtil.SetBitMask(ref numArray, writeWordInfo.DataLength, writeValue);
        var result1 = _socketService?.WriteWord(numArray, writeWordInfo.Address.To<int>(), 1, (int)writeWordInfo.AddrType, writeWordInfo.ReadBlock.To<int>());
        return result1 == true;
    }

    private bool WriteFloatValue(EquipTagDto writeWordInfo, string value)
    {
        int[]? num;
        if (writeWordInfo.DataLength == 2)
        {
            byte[] bytes = BitConverter.GetBytes(float.Parse(value));
            num = new int[] { BitConverter.ToInt16(bytes, 0), BitConverter.ToInt16(bytes, 2) };
            var result = _socketService?.WriteWord(num, writeWordInfo.Address.To<int>(), writeWordInfo.DataLength, (int)writeWordInfo.AddrType, writeWordInfo.ReadBlock.To<int>());
            return result == true;
        }
        if (writeWordInfo.DataLength != 4)
        {
            return false;
        }
        double num1 = 0;

        if (!double.TryParse(writeWordInfo.Value.ToString(), out num1))
        {
            return false;
        }
        byte[] numArray = BitConverter.GetBytes(num1);
        num = new int[] { BitConverter.ToInt16(numArray, 0), BitConverter.ToInt16(numArray, 2), BitConverter.ToInt16(numArray, 4), BitConverter.ToInt16(numArray, 6) };
        var result1 = _socketService?.WriteWord(num, writeWordInfo.Address.To<int>(), writeWordInfo.DataLength, (int)writeWordInfo.AddrType, writeWordInfo.ReadBlock.To<int>());
        return result1 == true;
    }

    private bool WriteIntValue(EquipTagDto writeWordInfo, string value)
    {
       
        int[]? int2;
        var result = int.TryParse(value, out int writeValue);
        if (!result)
        {
            return false;
        }
        if (writeWordInfo.DataLength == 1)
        {
            int2 = new int[] { writeValue };
           var result1 = _socketService?.WriteWord(int2, writeWordInfo.Address.To<int>(), writeWordInfo.DataLength, (int)writeWordInfo.AddrType, writeWordInfo.ReadBlock.To<int>());
            return result1 == true;
        }
        if (writeWordInfo.DataLength != 2)
        {
            return false;
        }
        int2 = PlcUtil.IntToInt2(writeValue);
       var result2 = _socketService?.WriteWord(int2, writeWordInfo.Address.To<int>(), writeWordInfo.DataLength, (int)writeWordInfo.AddrType, writeWordInfo.ReadBlock.To<int>());
        return result2 == true;
    }

    private bool WriteBit2Value(EquipTagDto writeWordInfo, string value)
    {
        int[] numArray = new int[1];
        bool result = int.TryParse(value, out int writeValue);
        if (!result)
        {
            return false;
        }
        int num = 0;
        m_UpdateReadWordInfoListLock.EnterReadLock();
        PlcReadWord? readWordInfo = CurReadWordList.FirstOrDefault((n) =>
        {
            if (n.MemoryType != writeWordInfo.AddrType || n.StartAddr != writeWordInfo.Address.To<int>())
            {
                return false;
            }
            return n.ReadCount == 1;
        });
        if (readWordInfo == null) return false;
        num = BitConverter.ToInt16(readWordInfo.CurReadValue, 0);
        m_UpdateReadWordInfoListLock.ExitReadLock();
        numArray[0] = num;
        PlcUtil.SetBit2Mask(ref numArray, writeWordInfo.DataLength, writeValue);
        var result1 = _socketService?.WriteWord(numArray, writeWordInfo.Address.To<int>(), 1, (int)writeWordInfo.AddrType, writeWordInfo.ReadBlock.To<int>());
        return result1 == true;
    }

    private bool WriteStringValue(EquipTagDto writeWordInfo, string value)
    {
        string writeValue = value;
        int[] num = PlcUtil.StringToInt(writeValue, writeWordInfo.DataLength);
        var result = _socketService?.WriteWord(num, writeWordInfo.Address.To<int>(), writeWordInfo.DataLength, (int)writeWordInfo.AddrType, writeWordInfo.ReadBlock.To<int>());
        return result == true;
    }


    #endregion
    /// <summary>
    /// 写入数据
    /// </summary>
    /// <param name="writeInfo"></param>
    /// <returns></returns>
    public async Task<bool> WriteRequest(OperationWriteCommand writeInfo)
    {
        // 查找写入点位是否存在
       var tag=  PLCTagList.FirstOrDefault(x => x.Id == writeInfo.TagId);
        if (tag == null)
        {
            return false;
        }
       return await Task.Run(() =>
        {
            bool flag = false;
            var dataType=(PlcDataType)tag.DataType;
            switch (dataType)
            {
                case PlcDataType.BIT:
                    {
                        flag = WriteBitValue(tag,writeInfo.Value);
                        return flag;
                    }
                case PlcDataType.INT16:
                    {
                        flag = WriteIntValue(tag, writeInfo.Value);
                        return flag;
                    }
                case PlcDataType.INT:
                    {
                        flag = WriteIntValue(tag, writeInfo.Value);
                        return flag;
                    }
                case PlcDataType.FLOAT:
                    {
                        flag = WriteFloatValue(tag, writeInfo.Value);
                        return flag;
                    }
                case PlcDataType.STR:
                    {
                        flag = WriteStringValue(tag, writeInfo.Value);
                        return flag;
                    }
                case PlcDataType.BIT4:
                    {
                        flag = WriteBit4Value(tag, writeInfo.Value);
                        return flag;
                    }
                case PlcDataType.BIT2:
                    {
                        flag = WriteBit2Value(tag, writeInfo.Value);
                        return flag;
                    }
            }
            return false;
        });
      
    }

    public void Dispose()
    {
        OperationStop();
    }
}
